#ifndef __TASKTAG__
#define __TASKTAG__

#include <array>
#include <tuple>
#include <iostream>

#include "../support/Threading.hpp"
#include "TupleHelper.hpp"
#include "constants.h"

using namespace std;

typedef int32_t tag_elem_t;
typedef bool bool_tag_elem_t;

template<int TASK_ID, int TASKTAGDIM, int TASKDIM>
class TaskTag {
  static const uint32_t primes[16];
public:
  static const int id = TASK_ID;
  static const int tasktagdim = TASKTAGDIM;
  static const int taskdim = TASKDIM;

  std::array<tag_elem_t, TASKTAGDIM> m_arr;

  TaskTag() = delete;

  template <typename... TagVals> constexpr 
  TaskTag(TagVals... tagVals) : m_arr{{std::forward<TagVals>(tagVals)...}}{
    static_assert(TASKTAGDIM == sizeof...(TagVals), "ERROR");
  }

  constexpr TaskTag(std::array<tag_elem_t, TASKTAGDIM> arr) : m_arr(arr) {}

  constexpr auto toTuple() const -> 
    decltype(arrayAsTuple<tag_elem_t, TASKTAGDIM>::make(m_arr)) 
  {
    return arrayAsTuple<tag_elem_t, TASKTAGDIM>::make(m_arr);
  }

  template<int OTHER_ID>
  constexpr TaskTag(const TaskTag<OTHER_ID, TASKTAGDIM, TASKDIM> &tt) 
  : m_arr(tt.m_arr) {}  

  constexpr bool operator==(const TaskTag &tt) const {
    return (tt.m_arr == m_arr);
  }

  // Find a workaround to make this constexpr
  // See for instance: https://gist.github.com/1457531
  tag_elem_t operator[](int i) const {
    return m_arr[i];
  }

  bool equals(const TaskTag<TASK_ID, TASKTAGDIM, TASKDIM> *tt) const {
    return this->equals(*tt);
  }

  bool equals(const TaskTag<TASK_ID, TASKTAGDIM, TASKDIM> &tt) const {
    for (int i=0; i<TASKTAGDIM; ++i)
      if (tt.m_arr[i] != m_arr[i]) return false;
    return true;
  }

  unsigned long hashCode() const {
    unsigned long res = 0;
#ifdef DEBUG
    SEQUENTIAL_DUMP1("gethash on " << *this << "\n");
#endif

    for (int i=0; i<TASKTAGDIM; ++i)
      res = (res + m_arr[i]) * primes[i] - 32;
    return res;
  }
};


template<int TASK_ID, int TASKTAGDIM, int TASKDIM>
const uint32_t TaskTag<TASK_ID, TASKTAGDIM, TASKDIM>::primes[16] = 
    {1, 31, 73, 127, 179, 233, 283, 353, 
     419, 467, 547, 607, 811, 877, 1019, 1229};

template<int TASK_ID, int TASKTAGDIM, int TASKDIM>
ostream& operator<<(ostream& stream, TaskTag<TASK_ID, TASKTAGDIM, TASKDIM> const &tt) {
  stream << "type=" << TASK_ID << ": ";
  for (int64_t i=0; i<TASKTAGDIM; ++i)
    stream << tt[i] << " ";  
  return stream;
}

#endif // __TASKTAG__
